home *** CD-ROM | disk | FTP | other *** search
/ Ian & Stuart's Australian Mac 1993 September / September 93.iso / Archives / Applications / Text / Text Editors / stevie 3.10 / cmdline.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-01-03  |  13.0 KB  |  727 lines  |  [TEXT/STvi]

  1. /* $Header: /nw/tony/src/stevie/src/RCS/cmdline.c,v 1.20 89/08/13 11:41:23 tony Exp $
  2.  *
  3.  * Routines to parse and execute "command line" commands, such as searches
  4.  * or colon commands.
  5.  */
  6.  
  7. #include "stevie.h"
  8.  
  9. static    char    *altfile = NULL;    /* alternate file */
  10. static    int    altline;        /* line # in alternate file */
  11.  
  12. static    char    *nowrtmsg = "No write since last change (use ! to override)";
  13. static    char    *nooutfile = "No output file";
  14. static    char    *morefiles = "more files to edit";
  15.  
  16. extern    char    **files;        /* used for "n" and "rew" */
  17. extern    int    numfiles, curfile;
  18.  
  19. #define    CMDSZ    100        /* size of the command buffer */
  20.  
  21. static    bool_t    doecmd();
  22. static    void    badcmd(), get_range();
  23. static    LPTR    *get_line();
  24.  
  25. /*
  26.  * getcmdln() - read a command line from the terminal
  27.  *
  28.  * Reads a command line started by typing '/', '?', '!', or ':'. Returns a
  29.  * pointer to the string that was read. For searches, an optional trailing
  30.  * '/' or '?' is removed.
  31.  */
  32. char *
  33. getcmdln(firstc)
  34. char    firstc;
  35. {
  36.     static    char    buff[CMDSZ];
  37.     register char    *p = buff;
  38.     register int    c;
  39.     register char    *q;
  40.  
  41.     gotocmd(TRUE, firstc);
  42.  
  43.     /* collect the command string, handling '\b' and @ */
  44.     do {
  45.         switch (c = vgetc()) {
  46.  
  47.         default:        /* a normal character */
  48.             outchar(c);
  49.             *p++ = c;
  50.             break;
  51.  
  52.         case BS:
  53.             if (p > buff) {
  54.                 /*
  55.                  * this is gross, but it relies
  56.                  * only on 'gotocmd'
  57.                  */
  58.                 p--;
  59.                 gotocmd(TRUE, firstc);
  60.                 for (q = buff; q < p ;q++)
  61.                     outchar(*q);
  62.             } else {
  63.                 msg("");
  64.                 return NULL;        /* back to cmd mode */
  65.             }
  66.             break;
  67.  
  68.         case '@':            /* line kill */
  69.             p = buff;
  70.             gotocmd(TRUE, firstc);
  71.             break;
  72.  
  73.         case NL:            /* done reading the line */
  74.         case CR:
  75.             break;
  76.         }
  77.     } while (c != NL && c != CR);
  78.  
  79.     *p = '\0';
  80.  
  81.     if (firstc == '/' || firstc == '?') {    /* did we do a search? */
  82.         /*
  83.          * Look for a terminating '/' or '?'. This will be the first
  84.          * one that isn't quoted. Truncate the search string there.
  85.          */
  86.         for (p = buff; *p ;) {
  87.             if (*p == firstc) {    /* we're done */
  88.                 *p = '\0';
  89.                 break;
  90.             } else if (*p == '\\')    /* next char quoted */
  91.                 p += 2;
  92.             else
  93.                 p++;        /* normal char */
  94.         }
  95.     }
  96.     return buff;
  97. }
  98.  
  99. /*
  100.  * docmdln() - handle a colon command
  101.  *
  102.  * Handles a colon command received interactively by getcmdln() or from
  103.  * the environment variable "EXINIT" (or eventually .virc).
  104.  */
  105. void
  106. docmdln(cmdline)
  107. char    *cmdline;
  108. {
  109.     char    buff[CMDSZ];
  110.     char    cmdbuf[CMDSZ];
  111.     char    argbuf[CMDSZ];
  112.     char    *cmd, *arg;
  113.     register char    *p;
  114.     /*
  115.      * The next two variables contain the bounds of any range given in a
  116.      * command. If no range was given, both contain null line pointers.
  117.      * If only a single line was given, u_pos will contain a null line
  118.      * pointer.
  119.      */
  120.     LPTR    l_pos, u_pos;
  121.  
  122.  
  123.     /*
  124.      * Clear the range variables.
  125.      */
  126.     l_pos.linep = (struct line *) NULL;
  127.     u_pos.linep = (struct line *) NULL;
  128.  
  129.     if (cmdline == NULL)
  130.         return;
  131.  
  132.     if (strlen(cmdline) > CMDSZ-2) {
  133.         msg("Error: command line too long");
  134.         return;
  135.     }
  136.     strcpy(buff, cmdline);
  137.  
  138.     /* skip any initial white space */
  139.     for (cmd = buff; *cmd != NUL && isspace(*cmd) ;cmd++)
  140.         ;
  141.  
  142.     if (*cmd == '%') {        /* change '%' to "1,$" */
  143.         strcpy(cmdbuf, "1,$");    /* kind of gross... */
  144.         strcat(cmdbuf, cmd+1);
  145.         strcpy(cmd, cmdbuf);
  146.     }
  147.  
  148.     while ((p=strchr(cmd, '%')) != NULL && *(p-1) != '\\') {
  149.                     /* change '%' to Filename */
  150.         if (Filename == NULL) {
  151.             emsg("No filename");
  152.             return;
  153.         }
  154.         *p= NUL;
  155.         strcpy (cmdbuf, cmd);
  156.         strcat (cmdbuf, Filename);
  157.         strcat (cmdbuf, p+1);
  158.         strcpy(cmd, cmdbuf);
  159.         msg(cmd);            /*repeat */
  160.     }
  161.  
  162.     while ((p=strchr(cmd, '#')) != NULL && *(p-1) != '\\') {
  163.                     /* change '#' to Altname */
  164.         if (altfile == NULL) {
  165.             emsg("No alternate file");
  166.             return;
  167.         }
  168.         *p= NUL;
  169.         strcpy (cmdbuf, cmd);
  170.         strcat (cmdbuf, altfile);
  171.         strcat (cmdbuf, p+1);
  172.         strcpy(cmd, cmdbuf);
  173.         msg(cmd);            /*repeat */
  174.     }
  175.  
  176.     /*
  177.      * Parse a range, if present (and update the cmd pointer).
  178.      */
  179.     get_range(&cmd, &l_pos, &u_pos);
  180.  
  181.     if (l_pos.linep != NULL) {
  182.         if (LINEOF(&l_pos) > LINEOF(&u_pos)) {
  183.             emsg("Invalid range");
  184.             return;
  185.         }
  186.     }
  187.  
  188.     strcpy(cmdbuf, cmd);    /* save the unmodified command */
  189.  
  190.     /* isolate the command and find any argument */
  191.     for ( p=cmd; *p != NUL && ! isspace(*p); p++ )
  192.         ;
  193.     if ( *p == NUL )
  194.         arg = NULL;
  195.     else {
  196.         *p = NUL;
  197.         for (p++; *p != NUL && isspace(*p) ;p++)
  198.             ;
  199.         if (*p == NUL)
  200.             arg = NULL;
  201.         else {
  202.             strcpy(argbuf, p);
  203.             arg = argbuf;
  204.         }
  205.     }
  206.     if (strcmp(cmd,"q!") == 0)
  207.         getout();
  208.     if (strcmp(cmd,"q") == 0) {
  209.         if (Changed)
  210.             emsg(nowrtmsg);
  211.         else {
  212.             if ((curfile + 1) < numfiles)
  213.                 emsg(morefiles);
  214.             else
  215.                 getout();
  216.         }
  217.         return;
  218.     }
  219.     if (strcmp(cmd,"w") == 0) {
  220.         if (arg == NULL) {
  221.             if (Filename != NULL) {
  222.                 writeit(Filename, &l_pos, &u_pos);
  223.             } else
  224.                 emsg(nooutfile);
  225.         }
  226.         else
  227.             writeit(arg, &l_pos, &u_pos);
  228.         return;
  229.     }
  230.     if (strcmp(cmd,"wq") == 0) {
  231.         if (Filename != NULL) {
  232.             if (writeit(Filename, (LPTR *)NULL, (LPTR *)NULL))
  233.                 getout();
  234.         } else
  235.             emsg(nooutfile);
  236.         return;
  237.     }
  238.     if (strcmp(cmd, "x") == 0) {
  239.         doxit();
  240.         return;
  241.     }
  242.  
  243.     if (strcmp(cmd,"f") == 0 && arg == NULL) {
  244.         fileinfo();
  245.         return;
  246.     }
  247.     if (*cmd == 'n') {
  248.         if ((curfile + 1) < numfiles) {
  249.             /*
  250.              * stuff ":e[!] FILE\n"
  251.              */
  252.             stuffin(":e");
  253.             if (cmd[1] == '!')
  254.                 stuffin("!");
  255.             stuffin(" ");
  256.             stuffin(files[++curfile]);
  257.             stuffin("\n");
  258.         } else
  259.             emsg("No more files!");
  260.         return;
  261.     }
  262.     if (*cmd == 'N') {
  263.         if (curfile > 0) {
  264.             /*
  265.              * stuff ":e[!] FILE\n"
  266.              */
  267.             stuffin(":e");
  268.             if (cmd[1] == '!')
  269.                 stuffin("!");
  270.             stuffin(" ");
  271.             stuffin(files[--curfile]);
  272.             stuffin("\n");
  273.         } else
  274.             emsg("No more files!");
  275.         return;
  276.     }
  277.     if (strncmp(cmd, "rew", 3) == 0) {
  278.         if (numfiles <= 1)        /* nothing to rewind */
  279.             return;
  280.         curfile = 0;
  281.         /*
  282.          * stuff ":e[!] FILE\n"
  283.          */
  284.         stuffin(":e");
  285.         if (cmd[3] == '!')
  286.             stuffin("!");
  287.         stuffin(" ");
  288.         stuffin(files[0]);
  289.         stuffin("\n");
  290.         return;
  291.     }
  292.     if (strcmp(cmd,"e") == 0 || strcmp(cmd,"e!") == 0) {
  293.         (void) doecmd(arg, cmd[1] == '!');
  294.         return;
  295.     }
  296.     /*
  297.      * The command ":e#" gets expanded to something like ":efile", so
  298.      * detect that case here.
  299.      */
  300.     if (*cmd == 'e' && arg == NULL) {
  301.         if (cmd[1] == '!')
  302.             (void) doecmd(&cmd[2], TRUE);
  303.         else
  304.             (void) doecmd(&cmd[1], FALSE);
  305.         return;
  306.     }
  307.     if (strcmp(cmd,"f") == 0) {
  308.         Filename = strsave(arg);
  309.         filemess("");
  310.         return;
  311.     }
  312.     if (strcmp(cmd,"r") == 0) {
  313.         if (arg == NULL) {
  314.             badcmd();
  315.             return;
  316.         }
  317.         if (readfile(arg, Curschar, 1)) {
  318.             emsg("Can't open file");
  319.             return;
  320.         }
  321.         updatescreen();
  322.         CHANGED;
  323.         return;
  324.     }
  325.     if (strcmp(cmd,"=") == 0) {
  326.         smsg("%d", cntllines(Filemem, &l_pos));
  327.         return;
  328.     }
  329.     if (strncmp(cmd,"ta", 2) == 0) {
  330.         dotag(arg, cmd[2] == '!');
  331.         return;
  332.     }
  333.     if (strncmp(cmd,"set", 2) == 0) {
  334.         doset(arg);
  335.         return;
  336.     }
  337.     if (strcmp(cmd,"help") == 0) {
  338.         if (help()) {
  339.             screenclear();
  340.             updatescreen();
  341.         }
  342.         return;
  343.     }
  344.     if (strncmp(cmd, "ve", 2) == 0) {
  345.         extern    char    *Version;
  346.  
  347.         msg(Version);
  348.         return;
  349.     }
  350.     if (strcmp(cmd, "sh") == 0) {
  351.         doshell(NULL);
  352.         return;
  353.     }
  354.     if (*cmd == '!') {
  355.         doshell(cmdbuf+1);
  356.         return;
  357.     }
  358.     if (strncmp(cmd, "s/", 2) == 0) {
  359.         dosub(&l_pos, &u_pos, cmdbuf+1);
  360.         return;
  361.     }
  362.     if (strncmp(cmd, "g/", 2) == 0) {
  363.         doglob(&l_pos, &u_pos, cmdbuf+1);
  364.         return;
  365.     }
  366.     /*
  367.      * If we got a line, but no command, then go to the line.
  368.      */
  369.     if (*cmd == NUL && l_pos.linep != NULL) {
  370.         *Curschar = l_pos;
  371.         return;
  372.     }
  373.  
  374.     badcmd();
  375. }
  376.  
  377.  
  378. doxit()
  379. {
  380.     if (Changed) {
  381.         if (Filename != NULL) {
  382.             if (!writeit(Filename, (LPTR *)NULL, (LPTR *)NULL))
  383.                 return;
  384.         } else {
  385.             emsg(nooutfile);
  386.             return;
  387.         }
  388.     }
  389.     if ((curfile + 1) < numfiles)
  390.         emsg(morefiles);
  391.     else
  392.         getout();
  393. }
  394.  
  395. /*
  396.  * get_range - parse a range specifier
  397.  *
  398.  * Ranges are of the form:
  399.  *
  400.  * addr[,addr]
  401.  *
  402.  * where 'addr' is:
  403.  *
  404.  * $  [+- NUM]
  405.  * 'x [+- NUM]    (where x denotes a currently defined mark)
  406.  * .  [+- NUM]
  407.  * NUM
  408.  *
  409.  * The pointer *cp is updated to point to the first character following
  410.  * the range spec. If an initial address is found, but no second, the
  411.  * upper bound is equal to the lower.
  412.  */
  413. static void
  414. get_range(cp, lower, upper)
  415. register char    **cp;
  416. LPTR    *lower, *upper;
  417. {
  418.     register LPTR    *l;
  419.     register char    *p;
  420.  
  421.     if ((l = get_line(cp)) == NULL)
  422.         return;
  423.  
  424.     *lower = *l;
  425.  
  426.     for (p = *cp; *p != NUL && isspace(*p) ;p++)
  427.         ;
  428.  
  429.     *cp = p;
  430.  
  431.     if (*p != ',') {        /* is there another line spec ? */
  432.         *upper = *lower;
  433.         return;
  434.     }
  435.  
  436.     *cp = ++p;
  437.  
  438.     if ((l = get_line(cp)) == NULL) {
  439.         *upper = *lower;
  440.         return;
  441.     }
  442.  
  443.     *upper = *l;
  444. }
  445.  
  446. static LPTR *
  447. get_line(cp)
  448. char    **cp;
  449. {
  450.     static    LPTR    pos;
  451.     LPTR    *lp;
  452.     register char    *p, c;
  453.     register int    lnum;
  454.  
  455.     pos.index = 0;        /* shouldn't matter... check back later */
  456.  
  457.     p = *cp;
  458.     /*
  459.      * Determine the basic form, if present.
  460.      */
  461.     switch (c = *p++) {
  462.  
  463.     case '$':
  464.         pos.linep = Fileend->linep->prev;
  465.         break;
  466.  
  467.     case '.':
  468.         pos.linep = Curschar->linep;
  469.         break;
  470.  
  471.     case '\'':
  472.         if ((lp = getmark(*p++)) == NULL) {
  473.             emsg("Unknown mark");
  474.             return (LPTR *) NULL;
  475.         }
  476.         pos = *lp;
  477.         break;
  478.  
  479.     case '0': case '1': case '2': case '3': case '4':
  480.     case '5': case '6': case '7': case '8': case '9':
  481.         for (lnum = c - '0'; isdigit(*p) ;p++)
  482.             lnum = (lnum * 10) + (*p - '0');
  483.  
  484.         pos = *gotoline(lnum);
  485.         break;
  486.  
  487.     default:
  488.         return (LPTR *) NULL;
  489.     }
  490.  
  491.     while (*p != NUL && isspace(*p))
  492.         p++;
  493.  
  494.     if (*p == '-' || *p == '+') {
  495.         bool_t    neg = (*p++ == '-');
  496.  
  497.         for (lnum = 0; isdigit(*p) ;p++)
  498.             lnum = (lnum * 10) + (*p - '0');
  499.  
  500.         if (neg)
  501.             lnum = -lnum;
  502.  
  503.         pos = *gotoline( cntllines(Filemem, &pos) + lnum );
  504.     }
  505.  
  506.     *cp = p;
  507.     return &pos;
  508. }
  509.  
  510. static void
  511. badcmd()
  512. {
  513.     emsg("Unrecognized command");
  514. }
  515.  
  516. #define    LSIZE    256    /* max. size of a line in the tags file */
  517.  
  518. /*
  519.  * dotag(tag, force) - goto tag
  520.  */
  521. void
  522. dotag(tag, force)
  523. char    *tag;
  524. bool_t    force;
  525. {
  526.     FILE    *tp;
  527.     char    lbuf[LSIZE];        /* line buffer */
  528.     char    pbuf[LSIZE];        /* search pattern buffer */
  529.     register char    *fname, *str;
  530.     register char    *p;
  531.  
  532.     if ((tp = fopen("tags", "r")) == NULL) {
  533.         emsg("Can't open tags file");
  534.         return;
  535.     }
  536.  
  537.     while (fgets(lbuf, LSIZE, tp) != NULL) {
  538.     
  539.         if ((fname = strchr(lbuf, TAB)) == NULL) {
  540.             emsg("Format error in tags file");
  541.             return;
  542.         }
  543.         *fname++ = '\0';
  544.         if ((str = strchr(fname, TAB)) == NULL) {
  545.             emsg("Format error in tags file");
  546.             return;
  547.         }
  548.         *str++ = '\0';
  549.  
  550.         if (strcmp(lbuf, tag) == 0) {
  551.  
  552.             /*
  553.              * Scan through the search string. If we see a magic
  554.              * char, we have to quote it. This lets us use "real"
  555.              * implementations of ctags.
  556.              */
  557.             p = pbuf;
  558.             *p++ = *str++;        /* copy the '/' or '?' */
  559.             *p++ = *str++;        /* copy the '^' */
  560.  
  561.             for (; *str != NUL ;str++) {
  562.                 if (*str == '\\') {
  563.                     *p++ = *str++;
  564.                     *p++ = *str;
  565.                 } else if (strchr("/?", *str) != NULL) {
  566.                     if (str[1] != '\n') {
  567.                         *p++ = '\\';
  568.                         *p++ = *str;
  569.                     } else
  570.                         *p++ = *str;
  571.                 } else if (strchr("^()*.", *str) != NULL) {
  572.                     *p++ = '\\';
  573.                     *p++ = *str;
  574.                 } else
  575.                     *p++ = *str;
  576.             }
  577.             *p = NUL;
  578.  
  579.             /*
  580.              * This looks out of order, but by calling stuffin()
  581.              * before doecmd() we keep an extra screen update
  582.              * from occuring. This stuffins() have no effect
  583.              * until we get back to the main loop, anyway.
  584.              */
  585.             stuffin(pbuf);        /* str has \n at end */
  586.             stuffin("\007");    /* CTRL('g') */
  587.  
  588.             if (doecmd(fname, force)) {
  589.                 fclose(tp);
  590.                 return;
  591.             } else
  592.                 stuffin(NULL);    /* clear the input */
  593.         }
  594.     }
  595.     emsg("tag not found");
  596.     fclose(tp);
  597. }
  598.  
  599. static    bool_t
  600. doecmd(arg, force)
  601. char    *arg;
  602. bool_t    force;
  603. {
  604.     int    line = 1;        /* line # to go to in new file */
  605.  
  606.     if (!force && Changed) {
  607.         emsg(nowrtmsg);
  608.         if (altfile)
  609.             free(altfile);
  610.         altfile = strsave(arg);
  611.         return FALSE;
  612.     }
  613.     if (arg != NULL) {
  614.         /*
  615.          * First detect a ":e" on the current file. This is mainly
  616.          * for ":ta" commands where the destination is within the
  617.          * current file.
  618.          */
  619.         if (Filename != NULL && strcmp(arg, Filename) == 0) {
  620.             if (!Changed || (Changed && !force))
  621.                 return TRUE;
  622.         }
  623.         if (altfile) {
  624.             if (strcmp (arg, altfile) == 0)
  625.                 line = altline;
  626.             free(altfile);
  627.         }
  628.         altfile = Filename;
  629.         altline = cntllines(Filemem, Curschar);
  630.         Filename = strsave(arg);
  631.     }
  632.     if (Filename == NULL) {
  633.         emsg("No filename");
  634.         return FALSE;
  635.     }
  636.  
  637.     /* clear mem and read file */
  638.     freeall();
  639.     filealloc();
  640.     UNCHANGED;
  641.  
  642.     if (readfile(Filename, Filemem, 0))
  643.         filemess("[New File]");
  644.  
  645.     *Topchar = *Curschar;
  646.     if (line != 1) {
  647.         stuffnum(line);
  648.         stuffin("G");
  649.     }
  650.     do_mlines();
  651.     setpcmark();
  652.     updatescreen();
  653.     return TRUE;
  654. }
  655.  
  656. void
  657. gotocmd(clr, firstc)
  658. bool_t  clr;
  659. char    firstc;
  660. {
  661.     windgoto(Rows-1,0);
  662.     if (clr)
  663.         outstr(T_EL);        /* clear the bottom line */
  664.     if (firstc)
  665.         outchar(firstc);
  666. }
  667.  
  668. /*
  669.  * msg(s) - displays the string 's' on the status line
  670.  */
  671. void
  672. msg(s)
  673. char    *s;
  674. {
  675.     gotocmd(TRUE, 0);
  676.     outstr(s);
  677.     flushbuf();
  678. }
  679.  
  680. /*VARARGS1*/
  681. void
  682. smsg(s, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)
  683. char    *s;
  684. int    a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16;
  685. {
  686.     char    sbuf[80];
  687.  
  688.     sprintf(sbuf, s,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16);
  689.     msg(sbuf);
  690. }
  691.  
  692. /*
  693.  * emsg() - display an error message
  694.  *
  695.  * Rings the bell, if appropriate, and calls message() to do the real work
  696.  */
  697. void
  698. emsg(s)
  699. char    *s;
  700. {
  701.     if (P(P_EB))
  702.         beep();
  703.     msg(s);
  704. }
  705.  
  706. void
  707. wait_return()
  708. {
  709.     register char    c;
  710.  
  711.     if (got_int)
  712.         outstr("Interrupt: ");
  713.  
  714.     outstr("Press RETURN to continue");
  715.     do {
  716.         c = vgetc();
  717.     } while (c != CR && c != NL && c != ' ' && c != ':');
  718.  
  719.     if (c == ':') {
  720.         outchar(NL);
  721.         docmdln(getcmdln(c));
  722.     } else
  723.         screenclear();
  724.  
  725.     updatescreen();
  726. }
  727.